forked from
leaflet.pub/leaflet
a tool for shared writing and social publishing
1import { z } from "zod";
2import {
3 PullRequest,
4 PullResponseV1,
5 VersionNotSupportedResponse,
6} from "replicache";
7import type { Fact } from "src/replicache";
8import { FactWithIndexes } from "src/replicache/utils";
9import type { Attribute } from "src/replicache/attributes";
10import { makeRoute } from "../lib";
11import type { Env } from "./route";
12
13// First define the sub-types for V0 and V1 requests
14const pullRequestV0 = z.object({
15 pullVersion: z.literal(0),
16 schemaVersion: z.string(),
17 profileID: z.string(),
18 cookie: z.any(), // ReadonlyJSONValue
19 clientID: z.string(),
20 lastMutationID: z.number(),
21});
22
23// For the Cookie type used in V1
24const cookieType = z.union([
25 z.null(),
26 z.string(),
27 z.number(),
28 z
29 .object({
30 order: z.union([z.string(), z.number()]),
31 })
32 .and(z.record(z.string(), z.any())), // ReadonlyJSONValue with order property
33]);
34
35const pullRequestV1 = z.object({
36 pullVersion: z.literal(1),
37 schemaVersion: z.string(),
38 profileID: z.string(),
39 cookie: cookieType,
40 clientGroupID: z.string(),
41});
42
43// Combined PullRequest type
44const PullRequestSchema = z.union([pullRequestV0, pullRequestV1]);
45
46export const pull = makeRoute({
47 route: "pull",
48 input: z.object({ pullRequest: PullRequestSchema, token_id: z.string() }),
49 handler: async ({ pullRequest, token_id }, { supabase }: Env) => {
50 let body = pullRequest;
51 if (body.pullVersion === 0) return versionNotSupported;
52 let { data, error } = await supabase.rpc("pull_data", {
53 token_id,
54 client_group_id: body.clientGroupID,
55 });
56 if (!data) {
57 console.log(error);
58
59 return {
60 error: "ClientStateNotFound",
61 } as const;
62 }
63
64 let facts = data.facts as {
65 attribute: string;
66 created_at: string;
67 data: any;
68 entity: string;
69 id: string;
70 updated_at: string | null;
71 version: number;
72 }[];
73 let publication_data = data.publications as {
74 description: string;
75 title: string;
76 tags: string[];
77 }[];
78 let pub_patch = publication_data?.[0]
79 ? [
80 {
81 op: "put",
82 key: "publication_description",
83 value: publication_data[0].description,
84 },
85 {
86 op: "put",
87 key: "publication_title",
88 value: publication_data[0].title,
89 },
90 {
91 op: "put",
92 key: "publication_tags",
93 value: publication_data[0].tags || [],
94 },
95 ]
96 : [];
97
98 let clientGroup = (
99 (data.client_groups as {
100 client_id: string;
101 client_group: string;
102 last_mutation: number;
103 }[]) || []
104 ).reduce(
105 (acc, clientRecord) => {
106 acc[clientRecord.client_id] = clientRecord.last_mutation;
107 return acc;
108 },
109 {} as { [clientID: string]: number },
110 );
111
112 return {
113 cookie: Date.now(),
114 lastMutationIDChanges: clientGroup,
115 patch: [
116 { op: "clear" },
117 { op: "put", key: "initialized", value: true },
118 ...(facts || []).map((f) => {
119 return {
120 op: "put",
121 key: f.id,
122 value: FactWithIndexes(f as unknown as Fact<Attribute>),
123 } as const;
124 }),
125 ...pub_patch,
126 ],
127 } as PullResponseV1;
128 },
129});
130
131const versionNotSupported: VersionNotSupportedResponse = {
132 error: "VersionNotSupported",
133 versionType: "pull",
134};